package own.stu.jobgib.playown.io.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import static own.stu.jobgib.playown.io.common.SocketOptionTest.Type.*;

public class TServerSocket {

    private static final Logger LOGGER = LoggerFactory.getLogger(TServerSocket.class.getName());

    private ServerSocket serverSocket_ = null;

    /**
     * Timeout for client sockets from accept
     */
    private int clientTimeout_ = 0;

    public TServerSocket(ServerSocket serverSocket) throws Exception {
        this(serverSocket, 0);
    }

    /**
     * Creates a server socket from underlying socket object
     */
    public TServerSocket(ServerSocket serverSocket, int clientTimeout) {
        this(new ServerSocketTransportArgs().serverSocket(serverSocket).clientTimeout(clientTimeout));
    }

    /**
     * Creates just a port listening server socket
     */
    public TServerSocket(int port) throws Exception {
        this(port, 0);
    }

    /**
     * Creates just a port listening server socket
     */
    public TServerSocket(int port, int clientTimeout) throws Exception {
        this(new InetSocketAddress(port), clientTimeout);
    }

    public TServerSocket(InetSocketAddress bindAddr) throws Exception {
        this(bindAddr, 0);
    }

    public TServerSocket(InetSocketAddress bindAddr, int clientTimeout) throws Exception {
        this(new ServerSocketTransportArgs().bindAddr(bindAddr).clientTimeout(clientTimeout));
    }

    public TServerSocket(ServerSocketTransportArgs args) {
        clientTimeout_ = args.clientTimeout;
        if (args.serverSocket != null) {
            this.serverSocket_ = args.serverSocket;
            return;
        }

        try {
            serverSocket_ = new ServerSocket();
            // Prevent 2MSL delay problem on server restarts
            serverSocket_.setReuseAddress(true);
            serverSocket_.bind(args.bindAddr, args.backlog);
        } catch (IOException e) {
            close();
            throw new RuntimeException("Could not create ServerSocket on address");
        }
    }

    public TSocket accept() {
        if (serverSocket_ == null) {
            throw new RuntimeException("No underlying server socket.");
        }

        Socket result;
        try {
            result = serverSocket_.accept();
        } catch (IOException e) {
            throw new RuntimeException(" accept error :", e);
        }

        if (result == null) {
            throw new RuntimeException("Blocking server's accept() may not return NULL");
        }

        TSocket socket = new TSocket(result, FromServer);
        socket.setTimeout(clientTimeout_);

        return socket;
    }

    public void listen() {
        if (serverSocket_ == null) {
            return;
        }

        try {
            //accept()  timeout time, set 0 表示无限一直等待
            serverSocket_.setSoTimeout(0);
        } catch (SocketException e) {
            LOGGER.error("Could not set socket timeout.", e);
        }
    }

    public void close() {

        if (serverSocket_ == null) {
            return;
        }

        try {
            serverSocket_.close();
        } catch (IOException iox) {
            LOGGER.warn("Could not close server socket.", iox);
        }
        serverSocket_ = null;
    }

    public void interrupt() {
        // The thread-safeness of this is dubious, but Java documentation suggests
        // that it is safe to do this from a different thread context
        close();
    }

    public ServerSocket getServerSocket() {
        return serverSocket_;
    }

    public static class ServerSocketTransportArgs {

        int backlog = 0; // A value of 0 means the default value will be used (currently set at 50)
        int clientTimeout = 0;
        InetSocketAddress bindAddr;

        ServerSocket serverSocket;

        public ServerSocketTransportArgs serverSocket(ServerSocket serverSocket) {
            this.serverSocket = serverSocket;
            return this;
        }

        public ServerSocketTransportArgs backlog(int backlog) {
            this.backlog = backlog;
            return this;
        }

        public ServerSocketTransportArgs clientTimeout(int clientTimeout) {
            this.clientTimeout = clientTimeout;
            return this;
        }

        public ServerSocketTransportArgs port(int port) {
            this.bindAddr = new InetSocketAddress(port);
            return this;
        }

        public ServerSocketTransportArgs bindAddr(InetSocketAddress bindAddr) {
            this.bindAddr = bindAddr;
            return this;
        }
    }

}